package org.springframework.beans.factory.config;

import org.springframework.beans.factory.anno.Configuration;
import org.springframework.beans.factory.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.beans.factory.util.ClassUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;

import java.util.*;

/**
 * @author:刘仁有
 * @desc:
 * @email:953506233@qq.com
 * @data:2019/12/16
 */
public final class ConfigurationClass {
    private final AnnotationMetadata metadata;

    private String beanName;

    private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);

    private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();


    private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
            new HashMap<>();

    final Set<String> skippedBeanMethods = new HashSet<>();



    /**
     * Create a new {@link ConfigurationClass} with the given name.
     * @param clazz the underlying {@link Class} to represent
     * @param beanName name of the {@code @Configuration} class bean
     * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
     */
    public ConfigurationClass(Class<?> clazz, String beanName) {
        Objects.requireNonNull(beanName, "Bean name must not be null");
        this.metadata = new StandardAnnotationMetadata(clazz);
        this.beanName = beanName;
    }

    public ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
        this.metadata = new StandardAnnotationMetadata(clazz);
        this.importedBy.add(importedBy);
    }

    public ConfigurationClass(AnnotationMetadata metadata, String beanName) {
        Objects.requireNonNull(beanName, "Bean name must not be null");
        this.metadata = metadata;
        this.beanName = beanName;
    }


    public AnnotationMetadata getMetadata() {
        return this.metadata;
    }


    public String getSimpleName() {
        return ClassUtils.getShortName(getMetadata().getClassName());
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getBeanName() {
        return this.beanName;
    }


    public boolean isImported() {
        return !this.importedBy.isEmpty();
    }

    public void mergeImportedBy(ConfigurationClass otherConfigClass) {
        this.importedBy.addAll(otherConfigClass.importedBy);
    }

    public Set<ConfigurationClass> getImportedBy() {
        return this.importedBy;
    }

    public void addBeanMethod(BeanMethod method) {
        this.beanMethods.add(method);
    }

    public Set<BeanMethod> getBeanMethods() {
        return this.beanMethods;
    }

    public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
        this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
    }

    public Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRegistrars() {
        return this.importBeanDefinitionRegistrars;
    }

    public void validate() {
        // A configuration class may not be final (CGLIB limitation)
        if (getMetadata().isAnnotated(Configuration.class)) {
            if (getMetadata().isFinal()) {
                throw new RuntimeException("FinalConfigurationProblem");
            }
        }

        for (BeanMethod beanMethod : this.beanMethods) {
            beanMethod.validate();
        }
    }

    @Override
    public boolean equals(Object other) {
        return (this == other || (other instanceof ConfigurationClass &&
                getMetadata().getClassName().equals(((ConfigurationClass) other).getMetadata().getClassName())));
    }

    @Override
    public int hashCode() {
        return getMetadata().getClassName().hashCode();
    }

    @Override
    public String toString() {
        return "ConfigurationClass: beanName '" + this.beanName;
    }

}
